using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using Microsoft.VisualStudio.TestTools.Common;
using Microsoft.VisualStudio.TestTools.Exceptions;
using Microsoft.VisualStudio.TestTools.Execution;
using TestResult = Microsoft.VisualStudio.TestTools.Common.TestResult;
using NUnit.Core;
using System.Globalization;
using NUnitForVSTS.Common;
using System.Security.Permissions;

namespace NUnitForVSTS.NUnitHandling
{
    /// <summary>
    /// Runner to execute the Nunit tests
    /// </summary>
    public class NUnitTestRunner : MarshalByRefObject, IDisposable
    {
        internal Dictionary<MethodInfo, KeyValuePair<object, bool>> fixtureTeardownMethods;
        internal Dictionary<MethodInfo, bool> fixtureSetup;
        internal Dictionary<MethodInfo, Exception> fixtureSetupExceptions;
        internal ManualResetEvent pauseExecutionEvent;

        private Dictionary<string, object> runContextProperties;

        private object testLock;
        private Guid testId;
        internal bool testStopped;

        private NUnitTestExecuter executer;
        private INUnitTestElement testElement;
        //private ITestContext testContext;
        internal IRunContext runContext;

        /// <summary>
        /// Initializes a new instance of the <see cref="NUnitTestRunner"/> class.
        /// </summary>
        /// <param name="runContext">The run context.</param>
        /// <param name="testRunId">The test run id.</param>
        public NUnitTestRunner(IRunContext runContext, Guid testRunId)
        {
            runContextProperties = new Dictionary<string, object>();
            testLock = new object();
            fixtureSetup = new Dictionary<MethodInfo, bool>();
            fixtureSetupExceptions = new Dictionary<MethodInfo, Exception>();
            fixtureTeardownMethods = new Dictionary<MethodInfo, KeyValuePair<object, bool>>();
            pauseExecutionEvent = new ManualResetEvent(true);
            this.runContext = runContext;
            testId = testRunId;
            if (runContext.RunContextVariables != null)
            {
                IDictionaryEnumerator enumerator = runContext.RunContextVariables.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    runContextProperties.Add(enumerator.Key.ToString(), enumerator.Value);
                }
            }
        }
        /// <summary>
        /// Runs the cleanup.
        /// </summary>
        public void RunCleanup()
        {
            ConsoleOutputRedirector outputRedirector = null;
            try
            {
                outputRedirector = new ConsoleOutputRedirector();
                RunCleanupInternal();
            }
            finally
            {
                if (outputRedirector != null)
                {
                    RunOutputTestResultMessage resultMessage = new RunOutputTestResultMessage(testId);
                    resultMessage.StdOut = outputRedirector.StdOut;
                    resultMessage.StdErr = outputRedirector.StdErr;
                    resultMessage.DebugTrace = outputRedirector.DebugTrace;
                    outputRedirector.Dispose();
                    try
                    {
                        runContext.ResultSink.AddResult(resultMessage);
                    }
                    catch (Exception exception1)
                    {
                        Trace.TraceError("Unexpected exception: " + exception1 + exception1.StackTrace);
                        throw;
                    }
                }
                else
                {
                    Trace.TraceWarning("Unable to setup trace redirector.");
                }
            }
        }
        /// <summary>
        /// Runs the cleanup internal.
        /// </summary>
        private void RunCleanupInternal()
        {
            pauseExecutionEvent.Reset();
            foreach (MethodInfo methodInfo in fixtureTeardownMethods.Keys)
            {
                KeyValuePair<object, bool> pair = fixtureTeardownMethods[methodInfo];
                if (pair.Value)
                {
                    try
                    {
                        methodInfo.Invoke(pair.Key, new object[0]);
                    }
                    catch (Exception tearDownException)
                    {
                        Exception innerException = tearDownException.InnerException;
                        if (innerException == null)
                        {
                            innerException = tearDownException;
                        }
                        TestRunTextResultMessage testResult = new TestRunTextResultMessage(testId, innerException.Message, innerException);
                        runContext.ResultSink.AddResult(testResult);
                    }
                }
            }
        }

        /// <summary>
        /// Resumes the test run.
        /// </summary>
        public void ResumeTestRun()
        {
            Trace.TraceInformation("NUnitTestRunner.ResumeTestRun was called.");
            pauseExecutionEvent.Set();
        }

        /// <summary>
        /// Stops the test run.
        /// </summary>
        /// <param name="outcome">The outcome.</param>
        public TestResult StopTestRun(TestOutcome outcome)
        {
            Trace.TraceInformation("NUnitTestRunner.StopTestRun was called.");
            TestResult testResult = null;
            lock (testLock)
            {
                if (testElement == null)
                    return testResult;
                if (testStopped)
                    return testResult;
                testStopped = true;

                if (executer != null)
                {
                    testResult = executer.PartialResult;
                    testResult.Outcome = outcome;
                }
            }
            pauseExecutionEvent.Set();
            return testResult;
        }

        /// <summary>
        /// Pauses the test run.
        /// </summary>
        public void PauseTestRun()
        {
            Trace.TraceInformation("NUnitTestRunner.PauseTestRun was called. Resetting the pause event.");
            pauseExecutionEvent.Reset();
        }

        /// <summary>
        /// Runs the specified test.
        /// </summary>
        /// <param name="test">The test.</param>
        /// <param name="result">The result.</param>
        /// <returns>Result of the test</returns>
        public TestResultMessage Run(INUnitTestElement test, TestResult result)
        {
            lock (testLock)
            {
                testElement = test;
                testStopped = false;
            }

            ConsoleOutputRedirector outputRedirector = null;
            try
            {
                outputRedirector = new ConsoleOutputRedirector();

                ExecuteSingleTest(result, test);
            }
            catch (Exception e)
            {
                result.Outcome = TestOutcome.Failed;
                if (e.InnerException is EqtException)
                {
                    result.ErrorMessage = (e.InnerException as EqtException).Message;
                    return result;
                }
                Trace.TraceError("Unit Test Adapter threw exception: {0}", new object[] { e });
                result.ErrorMessage = string.Format(CultureInfo.CurrentUICulture, "NUnitTestAdapter throws exception: {0}, {1}", e.GetType(), e.Message);
            }
            finally
            {
                lock (testLock)
                {
                    testElement = null;
                }
                if (outputRedirector != null)
                {
                    result.StdOut = outputRedirector.StdOut;
                    result.StdErr = outputRedirector.StdErr;
                    result.DebugTrace = outputRedirector.DebugTrace;
                    outputRedirector.Dispose();
                }
                else
                {
                    Trace.TraceWarning("Unable to setup trace redirector.");
                }
            }
            return result;
        }

        /// <summary>
        /// Executes the single test.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <param name="test">The test</param>
        private void ExecuteSingleTest(TestResult result, INUnitTestElement test)
        {
            NUnitTestExecuter testExecuter = new NUnitTestExecuter(test);
            lock (testLock)
            {
                executer = testExecuter;
            }
            try
            {
                testExecuter.Execute(result, this);
            }
            finally
            {
                lock (testLock)
                {
                    executer = null;
                }
            }
        }

        /// <summary>
        /// Obtains a lifetime service object to control the lifetime policy for this instance.
        /// </summary>
        /// <returns>
        /// An object of type <see cref="T:System.Runtime.Remoting.Lifetime.ILease"/> used to control the lifetime policy for this instance. This is the current lifetime service object for this instance if one exists; otherwise, a new lifetime service object initialized to the value of the <see cref="P:System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime"/> property.
        /// </returns>
        /// <exception cref="T:System.Security.SecurityException">
        /// The immediate caller does not have infrastructure permission.
        /// </exception>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="RemotingConfiguration, Infrastructure"/>
        /// </PermissionSet>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override object InitializeLifetimeService()
        {
            return null;
        }

        #region IDisposable
        private bool _disposed;
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (!(this._disposed))
            {
                if (disposing)
                {
                    pauseExecutionEvent.Close();
                }
            }
            this._disposed = true;
        }
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="NUnitTestRunner"/> is reclaimed by garbage collection.
        /// </summary>
        ~NUnitTestRunner()
        {
            Dispose(false);
        }
        #endregion
    }
}
